package jdbmaplite.manager.impl;

import java.io.FileInputStream;
import java.io.InputStream;
import java.sql.SQLException;
import java.util.Properties;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import jdbmaplite.connection.ConnectionFactory;
import jdbmaplite.connection.impl.BasicConnectionFactory;
import jdbmaplite.connection.impl.C3P0ConnectionFactory;
import jdbmaplite.connection.impl.DBCPConnectionFactory;
import jdbmaplite.connection.impl.JNDIConnectionFactory;
import jdbmaplite.connection.impl.ProxoolConnectionFactory;
import jdbmaplite.manager.DBManager;
import jdbmaplite.manager.ParseConfigException;
import jdbmaplite.util.DBUtils;
import jdbmaplite.util.LogUtils;
import jdbmaplite.util.StringUtils;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

public class JDBMapLiteManager implements DBManager {

	private static final Log _cat = LogFactory.getLog(JDBMapLiteManager.class);

	public static final String CONNECTION_POOL_ID_C3P0 = "c3p0";
	public static final String CONNECTION_POOL_ID_DBCP = "dbcp";
	public static final String CONNECTION_POOL_ID_JNDI = "jndi";
	public static final String CONNECTION_POOL_ID_PROXOOL = "proxool";

	private String configFilePath = null;
	private boolean isConfigFilePathAbsolutePath = false;
	private ConnectionFactory connectionFactory = null;
	private String daoSuffixFileName = null;
	private Element documentElement = null;

	/**
	 * Default constructor.
	 * 
	 * @param configFilePath
	 *            String
	 * @param isConfigFilePathAbsolutePath
	 *            boolean
	 * @throws ParseConfigException
	 * @throws ClassNotFoundException
	 */
	public JDBMapLiteManager(String configFilePath,
			boolean isConfigFilePathAbsolutePath) throws ParseConfigException,
			ClassNotFoundException {
		super();
		this.configFilePath = configFilePath;
		this.isConfigFilePathAbsolutePath = isConfigFilePathAbsolutePath;
		loadDataSourceDefinition(configFilePath, isConfigFilePathAbsolutePath);
	}

	public ConnectionFactory getConnectionFactory() {
		return connectionFactory;
	}

	public String getDaoSuffixFileName() {
		return daoSuffixFileName;
	}

	/**
	 * Load DataSource Definition
	 * 
	 * @param configFilePath
	 *            String
	 * @param isConfigFilePathAbsolutePath
	 *            boolean
	 * @throws ParseConfigException
	 */
	public void loadDataSourceDefinition(String configFilePath,
			boolean isConfigFilePathAbsolutePath) throws ParseConfigException {
		synchronized (this) {
			// basic properties
			String driver = null;
			String url = null;
			String user = null;
			String password = null;
			Properties connectionPoolProperties = null;
			String daoSuffixFileName = null;
			String connectionPoolId = null;
			// parse configuration
			DocumentBuilderFactory factory = null;
			InputStream is = null;
			try {
				is = (isConfigFilePathAbsolutePath ? new FileInputStream(
						configFilePath) : getClass().getResourceAsStream(
						configFilePath));
				factory = DocumentBuilderFactory.newInstance();
				DocumentBuilder builder = factory.newDocumentBuilder();
				Document dataSourceManagerDoc = builder.parse(is);
				// data source manager
				documentElement = dataSourceManagerDoc.getDocumentElement();
				daoSuffixFileName = documentElement
						.getAttribute("daoSuffixFileName");
				if (daoSuffixFileName == null) {
					daoSuffixFileName = "";
				}
				// init basic properties
				Element basicDataSource = getChildElementByTagName("dataSource");
				if (basicDataSource != null) {
					Properties properties = getChildProperties(basicDataSource);
					driver = properties.getProperty("driver", "");
					url = properties.getProperty("url", "");
					user = properties.getProperty("user", "");
					password = properties.getProperty("password", "");
				}
				// init connnection factory
				Element connectionPool = this
						.getChildElementByTagName("connectionPool");
				if (connectionPool != null) {
					connectionPoolId = connectionPool.getAttribute("id");
				}
				connectionPoolProperties = getChildProperties(connectionPool);
			} catch (Throwable e) {
				_cat.warn("Parse config failed!", e);
				throw new ParseConfigException(e);
			} finally {
				DBUtils.closeQuietly(is);
			}
			// destroy
			if (this.connectionFactory != null) {
				try {
					this.connectionFactory.destroy();
				} catch (SQLException e) {
					_cat.warn("Destroy old connectionFactory failed!", e);
					throw new RuntimeException(e);
				}
			}
			// reset connection factory
			this.configFilePath = configFilePath;
			this.isConfigFilePathAbsolutePath = isConfigFilePathAbsolutePath;
			this.connectionFactory = createConnectionFactory(connectionPoolId,
					driver, url, user, password, connectionPoolProperties);
			this.daoSuffixFileName = daoSuffixFileName;
			if (_cat.isInfoEnabled()) {
				_cat.info(LogUtils.formatDebugLog(
						"Init data source properties",
						"driver=%s, url=%s, user=%s, password=%s", driver, url,
						user, "******"));
				_cat.info(LogUtils.formatDebugLog("Init dao properties",
						"daoSuffixFileName=%s", daoSuffixFileName));
			}
		}
	}

	/**
	 * Reload DataSource Definition
	 * 
	 * @throws ParseConfigException
	 */
	public void reloadDataSourceDefinition() throws ParseConfigException {
		loadDataSourceDefinition(configFilePath, isConfigFilePathAbsolutePath);
	}

	/**
	 * Create Connection Factory
	 * 
	 * @param connectionPoolId
	 *            String
	 * @param driver
	 *            String
	 * @param url
	 *            String
	 * @param user
	 *            String
	 * @param password
	 *            String
	 * @param properties
	 *            Properties
	 * @return ConnectionFactory
	 * @throws ParseConfigException
	 */
	public ConnectionFactory createConnectionFactory(String connectionPoolId,
			String driver, String url, String user, String password,
			Properties properties) throws ParseConfigException {
		ConnectionFactory connectionFactory = null;
		try {
			if (StringUtils.isAnyEmpty(connectionPoolId)) {
				connectionFactory = new BasicConnectionFactory(driver, url,
						user, password);
			} else if (connectionPoolId.equals(CONNECTION_POOL_ID_C3P0)) {
				connectionFactory = new C3P0ConnectionFactory(driver, url,
						user, password);
			} else if (connectionPoolId.equals(CONNECTION_POOL_ID_DBCP)) {
				connectionFactory = new DBCPConnectionFactory(driver, url,
						user, password);
			} else if (connectionPoolId.equals(CONNECTION_POOL_ID_JNDI)) {
				connectionFactory = new JNDIConnectionFactory(url);
			} else if (connectionPoolId.equals(CONNECTION_POOL_ID_PROXOOL)) {
				connectionFactory = new ProxoolConnectionFactory(driver, url,
						user, password);
			}
			if (_cat.isInfoEnabled()) {
				_cat.info(LogUtils.formatDebugLog("Init connection factory",
						(connectionFactory != null ? connectionFactory
								.getClass().getSimpleName() : null)));
			}
			if (connectionFactory != null) {
				// load properties
				connectionFactory.loadProperties(properties);
			}
			if (_cat.isInfoEnabled()) {
				// hide password
				Properties clonedProperties = (Properties) properties.clone();
				// driver=%s, url=%s, user=%s, password
				clonedProperties.remove("driver");
				clonedProperties.remove("url");
				clonedProperties.remove("user");
				clonedProperties.remove("password");
				_cat.info(LogUtils.formatDebugLog(
						"Init connection pool properties",
						clonedProperties.toString()));
			}
		} catch (Throwable e) {
			_cat.warn("Parse config failed!", e);
			throw new ParseConfigException(e);
		}
		return connectionFactory;
	}

	/**
	 * Get Child Element By Tag Name
	 * 
	 * @param tagName
	 *            String
	 * @return Element
	 */
	public Element getChildElementByTagName(String tagName) {
		if (documentElement == null) {
			throw new RuntimeException("DocumentElement not found!");
		}
		if (tagName == null) {
			return null;
		}
		NodeList nodeList = documentElement.getElementsByTagName(tagName);
		int nodeListSize = nodeList.getLength();
		if (nodeListSize > 0) {
			return (Element) nodeList.item(nodeListSize - 1);
		}
		return null;
	}

	/**
	 * Get Child Properties
	 * 
	 * @param element
	 *            Element
	 * @return Properties
	 */
	public Properties getChildProperties(Element element) {
		Properties properties = new Properties();
		if (element != null) {
			NodeList nodeList = element.getElementsByTagName("property");
			for (int i = 0, nodeListSize = nodeList.getLength(); i < nodeListSize; i++) {
				Element property = (Element) nodeList.item(i);
				String propertyName = property.getAttribute("name");
				if (StringUtils.isAnyEmpty(propertyName)) {
					continue;
				}
				String propertyValue = null;
				if ((propertyValue = property.getTextContent()) == null) {
					propertyValue = "";
				}
				properties.put(propertyName, propertyValue);
			}
		}
		return properties;
	}

	/**
	 * Get Config File Path
	 * 
	 * @return String
	 */
	public String getConfigFilePath() {
		return configFilePath;
	}

	/**
	 * Is Config File Path Absolute Path
	 * 
	 * @return boolean
	 */
	public boolean isConfigFilePathAbsolutePath() {
		return isConfigFilePathAbsolutePath;
	}

	/**
	 * Get Document Element
	 * 
	 * @return Element
	 */
	public Element getDocumentElement() {
		return documentElement;
	}

}